﻿// ------------------------------------------------------------------------------------------------
// <copyright file="XmlDiffPatchHelper.cs" company="Hiyoko">
//   Copyright (c) 2015 All Rights Reserved
// </copyright>
// <summary>
//   This helper class provide methods for comparing two XML files and generating HTML output.
//   The Microsoft library used is XmlDiffPatch: http://msdn.microsoft.com/en-us/library/aa302294.aspx
// </summary>
// ---------------------------------------------------------------------------------------------

using Microsoft.XmlDiffPatch;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

namespace Hiyoko.SolutionsComparer.Helper
{
    public static class XmlDiffPatchHelper
    {
        #region Public methods

        /// <summary>
        /// Compare two XML documents and generate XML Diff XML document.
        /// Source: http://msdn.microsoft.com/en-us/library/aa302294.aspx
        /// </summary>
        /// <param name="sourceXmlPath">The source XML file path</param>
        /// <param name="changedXmlPath">The changed XML file path</param>
        /// <param name="diffXmlPath">The diff gram XML file path</param>
        public static void Compare(string sourceXmlPath, string changedXmlPath, string diffXmlPath)
        {
            if (string.IsNullOrEmpty(sourceXmlPath))
            {
                throw new ArgumentException("The sourceXmlPath argument is null or empty.");
            }

            if (string.IsNullOrEmpty(changedXmlPath))
            {
                throw new ArgumentException("The changedXmlPath argument is null or empty.");
            }

            if (string.IsNullOrEmpty(diffXmlPath))
            {
                throw new ArgumentException("The diffXmlPath argument is null or empty.");
            }

            using (var xmlWriter = XmlWriter.Create(diffXmlPath))
            {
                var diffOptions = XmlDiffOptions.None;
                diffOptions = diffOptions | XmlDiffOptions.IgnorePI;
                diffOptions = diffOptions | XmlDiffOptions.IgnoreChildOrder;
                diffOptions = diffOptions | XmlDiffOptions.IgnoreComments;
                diffOptions = diffOptions | XmlDiffOptions.IgnoreDtd;
                diffOptions = diffOptions | XmlDiffOptions.IgnoreNamespaces;
                diffOptions = diffOptions | XmlDiffOptions.IgnorePrefixes;
                //diffOptions = diffOptions | XmlDiffOptions.IgnoreWhitespace;
                diffOptions = diffOptions | XmlDiffOptions.IgnoreXmlDecl;

                var xmldiff = new XmlDiff
                {
                    Algorithm = XmlDiffAlgorithm.Fast,
                    Options = diffOptions
                };

                xmldiff.Compare(sourceXmlPath, changedXmlPath, false, xmlWriter);
            }
        }

        /// <summary>
        /// Format XML result into HTML table
        /// Source: http://msdn.microsoft.com/en-us/library/aa302294.aspx
        /// </summary>
        /// <param name="sourceXmlFile">The source XML file path</param>
        /// <param name="diffGramPath">The XML diff. gram. file path</param>
        /// <param name="htmlResultPath">The HTML result file path</param>
        public static void FormatResult(string sourceXmlFile, string diffGramPath, string htmlResultPath)
        {
            if (string.IsNullOrEmpty(sourceXmlFile))
            {
                throw new ArgumentException("The sourceXmlFile argument is null or empty.");
            }

            if (string.IsNullOrEmpty(diffGramPath))
            {
                throw new ArgumentException("The diffGramPath argument is null or empty.");
            }

            if (string.IsNullOrEmpty(htmlResultPath))
            {
                throw new ArgumentException("The htmlResultPath argument is null or empty.");
            }

            using (var sourceReader = new XmlTextReader(sourceXmlFile))
            using (var diffReader = new XmlTextReader(diffGramPath))
            using (var htmlWriter = new StreamWriter(htmlResultPath))
            {
                var xmlDiffView = new XmlDiffView();
                xmlDiffView.Load(sourceReader, diffReader);

                //Wrapping
                htmlWriter.Write("<html><body><table>");

                htmlWriter.Write("<tr><td>");
                htmlWriter.Write("<b>Legend:</b><br/><br/>");
                htmlWriter.Write("<font style='backgroundcolor: yellow' color='black'>added</font><br/>");
                htmlWriter.Write("<font style='background-color: red' color='black'>removed</font><br/>");
                htmlWriter.Write("<font style='background-color: lightgreen' color='black'>changed</font><br/>");
                htmlWriter.Write("<font style='background-color: red' color='blue'>moved from</font><br/>");
                htmlWriter.Write("<font style='background-color: yellow' color='blue'>moved to</font><br/>");
                htmlWriter.Write("<font style='background-color: white' color='#AAAAAA'>ignored</font><br/>");
                htmlWriter.Write("</td><td></td></tr>");

                htmlWriter.Write("<tr><td><b>");
                htmlWriter.Write("Source");
                htmlWriter.Write("</b></td><td><b>");
                htmlWriter.Write("Changed");
                htmlWriter.Write("</b></td></tr>");

                xmlDiffView.GetHtml(htmlWriter);

                htmlWriter.Write("</table></body></html>");
            }

            CleanXmlFile(htmlResultPath);

            var doc = XDocument.Load(htmlResultPath);
            var iteration = 0;
            foreach (var row in doc.XPathSelectElements("//tr"))
            {
                var lineNode = new XElement("td")
                {
                    Value = iteration < 2 ? string.Empty : (iteration - 1).ToString(CultureInfo.InvariantCulture)
                };
                row.AddFirst(lineNode);
                iteration++;
            }

            var settings = new XmlWriterSettings
            {
                Indent = true,
                IndentChars = "  ",
                NewLineChars = "\r\n",
                NewLineHandling = NewLineHandling.Replace,
                OmitXmlDeclaration = true
            };

            using (var writer = XmlWriter.Create(htmlResultPath, settings))
            {
                doc.Save(writer);
            }
        }

        /// <summary>
        /// Format diff only HTML result document
        /// </summary>
        /// <param name="sourceHtmlPath">The source HTML result file path</param>
        /// <param name="targetHtmlPath">The target HTML result file path</param>
        public static void FormatDiffResult(string sourceHtmlPath, string targetHtmlPath)
        {
            if (string.IsNullOrEmpty(sourceHtmlPath))
            {
                throw new ArgumentException("The htmlResultPath argument is null or empty.");
            }

            if (string.IsNullOrEmpty(targetHtmlPath))
            {
                throw new ArgumentException("The targetHtmlPath argument is null or empty.");
            }

            var doc = XDocument.Load(sourceHtmlPath);
            const string xpath = @"//tr[./td/font[(@style=""background-color: red"" and @color=""black"") or (@style=""background-color: red"" and @color=""blue"") or (@style=""background-color: yellow"" and @color=""black"") or (@style=""background-color: yellow"" and @color=""blue"") or (@style=""background-color: lightgreen"" and @color=""black"")]]";
            var rows = doc.XPathSelectElements(xpath);

            var output = new XDocument(
                new XElement("html",
                    new XElement("body",
                        new XElement("table"))
                )
            );

            var tableElement = output.Descendants("table").Single();
            if (tableElement == null)
            {
                throw new ApplicationException("An error occured: XMLElement should not be null at this point.");
            }
            tableElement.Add(rows);

            var references = output.Descendants("a").ToList();
            foreach (var reference in references)
            {
                foreach (var child in reference.Elements())
                {
                    reference.PreviousNode.AddAfterSelf(child);
                }
            }
            output.Descendants("a").Remove();

            var settings = new XmlWriterSettings
            {
                Indent = true,
                IndentChars = "  ",
                NewLineChars = "\r\n",
                NewLineHandling = NewLineHandling.Replace,
                OmitXmlDeclaration = true
            };

            using (var writer = XmlWriter.Create(targetHtmlPath, settings))
            {
                output.Save(writer);
            }
        }

        #endregion Public methods

        #region Private methods

        /// <summary>
        /// Clean an XML file.
        /// </summary>
        /// <param name="xmlFilePath">The XML file path</param>
        private static void CleanXmlFile(string xmlFilePath)
        {
            if (string.IsNullOrEmpty(xmlFilePath))
            {
                throw new ArgumentException("The xmlFilePath argument is null or empty.");
            }

            var xmlContent = File.ReadAllText(xmlFilePath);
            xmlContent = xmlContent.Replace("&nbsp;", "&#160;");
            xmlContent = xmlContent.Replace("&lt;", "&#60;");
            xmlContent = xmlContent.Replace("&gt;", "&#62;");
            xmlContent = xmlContent.Replace("&amp;", "&#38;");
            File.WriteAllText(xmlFilePath, xmlContent);
        }

        #endregion Private methods
    }
}
